package org.gendut.collection.mutable;

import org.gendut.collection.ConstantArray;
import org.gendut.collection.Void;
import org.gendut.func.Function;
import org.gendut.func.Pair;
import org.gendut.func.Producer;

/**
 * Array computation that completely hides the internal state
 */
public final class ArrayST<E, O> {

  /*
   * Actually, the signature is E[]->(E[],O), and NOT (E[],O)->(E[],O). The
   * second part of the input pair is always ignored!
   */
  private Transformer<Pair<Object[], Object>, Pair<Object[], Object>> m;

  private ArrayST(Transformer<Pair<Object[], Object>, Pair<Object[], Object>> m) {
    this.m = m;
  }

  @SuppressWarnings({ "rawtypes", "unchecked" })
  private final static ArrayST identity = new ArrayST(Transformer.ret());

  @SuppressWarnings("unchecked")
  public static <E> ArrayST<E, Void> ret() {
    return identity;
  }

  public static <E, O> ArrayST<E, O> ret(final O out) {
    return new ArrayST<E, O>(
        new Transformer<Pair<Object[], Object>, Pair<Object[], Object>>() {
          public Pair<Object[], Object> value(Pair<Object[], Object> s) {
            return Pair.create(s.first(), (Object) out);
          }
        });
  }

  public static <E> ArrayST<E, Integer> size() {
    return new ArrayST<E, Integer>(
        new Transformer<Pair<Object[], Object>, Pair<Object[], Object>>() {
          public Pair<Object[], Object> value(Pair<Object[], Object> s) {
            Object[] arr = s.first();
            return Pair.create(arr, (Object) arr.length);
          }
        });
  }

  public static <E> ArrayST<E, ConstantArray<E>> asConstant() {
    return new ArrayST<E, ConstantArray<E>>(
        new Transformer<Pair<Object[], Object>, Pair<Object[], Object>>() {
          public Pair<Object[], Object> value(Pair<Object[], Object> s) {
            Object[] arr = s.first();
            return Pair.create(arr, (Object) ConstantArray.fromArray(arr));
          }
        });
  }

  public static <E, O> ArrayST<E, O> onSize(Function<Integer, ArrayST<E, O>> fun) {
    ArrayST<E, Integer> s = size();
    return s.bind(fun);
  }

  public static <E> ArrayST<E, E> read(final int i) {
    return new ArrayST<E, E>(
        new Transformer<Pair<Object[], Object>, Pair<Object[], Object>>() {
          public Pair<Object[], Object> value(Pair<Object[], Object> s) {
            Object[] arr = s.first();
            return Pair.create(arr, arr[i]);
          }
        });
  }

  public static <E, P> ArrayST<E, P> onRead(int i,
      Function<E, ArrayST<E, P>> fun) {
    ArrayST<E, E> r = read(i);
    return r.bind(fun);
  }

  public static <E> ArrayST<E, Void> write(final int i, final E x) {
    return new ArrayST<E, Void>(
        new Transformer<Pair<Object[], Object>, Pair<Object[], Object>>() {
          public Pair<Object[], Object> value(Pair<Object[], Object> s) {
            Object[] arr = s.first();
            arr[i] = x;
            return Pair.create(arr, null);
          }
        });
  }

  public <P> ArrayST<E, P> bind(final Producer<ArrayST<E, P>> other) {
    return bind(new Function<O, ArrayST<E, P>>() {
      public ArrayST<E, P> get(O x) {
        return other.get();
      }
    });
  }

  public <P> ArrayST<E, P> bind(final Function<O, ArrayST<E, P>> other) {
    return new ArrayST<E, P>(
        m.bind(new Function<Pair<Object[], Object>, Transformer<Pair<Object[], Object>, Pair<Object[], Object>>>() {
          @SuppressWarnings("unchecked")
          public Transformer<Pair<Object[], Object>, Pair<Object[], Object>> get(
              Pair<Object[], Object> e) {
            return other.get((O) e.second()).m;
          }
        }));
  }

  public O run(int size) {
    @SuppressWarnings("unchecked")
    O r = (O) m.value(Pair.create(new Object[size], null)).second();
    return r;
  }
}
